package Logica;

import static Logica.Cliente.janelaPrincipal;
import static Logica.Cliente.ts;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Repositorio {

    public static final int MAX_SIZE = 4000;
    public static final int TIMEOUT = 5; //segundos
    public static InetAddress IPserv;
    public static DatagramSocket dataSock;
    public static DatagramPacket dataPack;
    public static DatagramSocket dataSock2;
    public static DatagramPacket dataPack2;
    private static List<Ficheiro> file;
    private static ServerSocket sockR;
    public static String localDirectory = "C:\\Users\\NuNo\\Dropbox\\PD\\TP_pdist\\repositorio1";
    //public static String localDirectory = "C:\\Users\\Rui Margarido\\Dropbox\\PD\\TP_pdist\\repositorio1";
    public static int UDPport;
    public static int TCPportRep = 10000;
    public static ServerSocket servSockRep;
    private static List<Socket> clientes;
    private static List<Socket> repositorios;
    int nbytes;
    byte[] fileChunck = new byte[MAX_SIZE];
    static GereHeartBeat t;
    public static GereCliente t2;
    public static int id = 0;
    public static int idPrincipalRepositorio;
    public static GereRepositorio t3;
    
    public class GereHeartBeat extends Thread {

        @Override
        public void run() {
            while (true) {
                try {
                    heartBeat(false);
                    Thread.sleep(20000);
                } catch (InterruptedException ex) {
                    Logger.getLogger(Repositorio.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        }
    }

    public class GereCliente extends Thread {

        int flag;
        String nomeFicheiro;
        int thisId;

        public int getThisId() {
            return thisId;
        }

        public void setThisId(int id) {
            this.thisId = id;
        }

        public String getNomeFicheiro() {
            return nomeFicheiro;
        }

        public void setNomeFicheiro(String nomeFicheiro) {
            this.nomeFicheiro = nomeFicheiro;
        }

        public int getFlag() {
            return flag;
        }

        public void setFlag(int flag) {
            this.flag = flag;
        }

        public GereCliente() {
        }

        public GereCliente(int flag) {
            this.flag = flag;
        }

        public GereCliente(int flag, String msg) {
            this.flag = flag;
            this.nomeFicheiro = msg;
        }

        public void run() {
            switch (flag) {
                case 1: //download
                    enviaFicheiro(thisId, nomeFicheiro);
                    heartBeat(true);
                    clientes.remove(thisId);
                    break;

                case 2: //upload

                    long tamFich = recebeFicheiro(thisId, nomeFicheiro);
                    File f = new File(localDirectory + "\\" + nomeFicheiro);
                    file.add(new Ficheiro(f, nomeFicheiro, tamFich, new Date(f.lastModified())));
                    System.out.println(toStringListaFicheiros());
                    heartBeat(true);
                    clientes.remove(thisId);
                    break;

                case 3:// eliminar
                    int index = EncontraIndexFicheiro(nomeFicheiro);
                    if (index == -1) {
                        System.out.println("index nao encontrado");
                    } else {
                        file.get(index).file.delete();
                        file.remove(index);
                    }
                    System.out.println(toStringListaFicheiros());
                    heartBeat(true);
                    break;
            }
        }
    }

    public class GereRepositorio extends Thread {
        
        public void run() {
            try {
                while (true) {
                    repositorios.add(servSockRep.accept());
                }
            } catch (IOException ex) {
                Logger.getLogger(Repositorio.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }

    public static class GereAtualizacoesRepositorio extends Thread {

        public GereAtualizacoesRepositorio(){
            this.start();
        }

        public void run() {
            List<Ficheiro> lista;
            ObjectInputStream recebe = null;
            try {
                recebe = new ObjectInputStream(ts.getInputStream());
                sleep(2000);
                while (true) {
                    System.out.println("carregar lista");

                    file = (List<Ficheiro>) recebe.readObject();
                    janelaPrincipal.CarregaLista();

                    System.out.println("lista carregada");
                }

            } catch (IOException ex) {
                Logger.getLogger(Cliente.class.getName()).log(Level.SEVERE, null, ex);
            } catch (ClassNotFoundException ex) {
                Logger.getLogger(Cliente.class.getName()).log(Level.SEVERE, null, ex);
            } catch (InterruptedException ex) {
                Logger.getLogger(Cliente.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }

    public void atualizaRepositorios() {
        for (int i = 0; i < repositorios.size(); i++) {

        }
    }

    public Repositorio(int udpport) {
        try {
            UDPport = Integer.parseInt("56000");
            t = new GereHeartBeat();
            t2 = new GereCliente();
            dataPack = new DatagramPacket(new byte[256], 256, IPserv, UDPport);
            dataSock = new DatagramSocket();
            //sockR = new ServerSocket(9002);
            clientes = new ArrayList<Socket>();
            repositorios = new ArrayList<Socket>();
            t3 = new GereRepositorio();

            //localDirectory = "C:\\Users\\Rui Margarido\\Dropbox\\PD\\TP_pdist\\repositorio1".trim();
            //localDirectory = "C:\\Users\\NuNo\\\\Dropbox\\PD\\\\TP_pdist\\repositorio1".trim();
            IPserv = InetAddress.getByName("127.0.0.1".trim());
        } catch (SocketException ex) {
            Logger.getLogger(Repositorio.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(Repositorio.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public Repositorio(String path, int udpport, String ip) {
        try {
            UDPport = Integer.parseInt("56000");
            IPserv = InetAddress.getByName(ip);
            t = new GereHeartBeat();
            t2 = new GereCliente();
            dataPack = new DatagramPacket(new byte[256], 256, IPserv, UDPport);
            dataSock = new DatagramSocket();
            //sockR = new ServerSocket(9001);
            clientes = new ArrayList<Socket>();
            repositorios = new ArrayList<Socket>();
            t3 = new GereRepositorio();

            //localDirectory = "C:\\Users\\Rui Margarido\\Dropbox\\PD\\TP_pdist\\repositorio1".trim();
            localDirectory = "C:\\Users\\NuNo\\Dropbox\\PD\\TP_pdist\\repositorio1".trim();
            IPserv = InetAddress.getByName("127.0.0.1".trim());
        } catch (SocketException ex) {
            Logger.getLogger(Repositorio.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(Repositorio.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public List<Ficheiro> getFile() {
        return file;
    }

    public void setFile(List<Ficheiro> file) {
        this.file = file;
    }

    String getHeartBeats() {
        return "" + (clientes.size() + repositorios.size());
    }

    void heartBeat(boolean updated) {
        Mensagem sms;
        if (updated) {
            sms = new Mensagem(getHeartBeats(), file, idPrincipalRepositorio);
        } else {
            sms = new Mensagem(getHeartBeats(), idPrincipalRepositorio);
        }
        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
        ObjectOutputStream out;
        try {
            out = new ObjectOutputStream(bOut);
            out.writeObject(sms);
            out.flush();
        } catch (IOException ex) {
            Logger.getLogger(Repositorio.class.getName()).log(Level.SEVERE, null, ex);
        }

            //out.writeUnshared(objectToTransmit) in order to avoid caching issues
        dataPack.setData(bOut.toByteArray());
        dataPack.setLength(bOut.size());
        try {
            dataSock.send(dataPack);
        } catch (IOException ex) {
            System.out.println("ERRO1");
            Logger.getLogger(Repositorio.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public long recebeFicheiro(int id, String nomeFicheiro) {
        File localDir = new File(localDirectory);
        FileOutputStream requestedFileOutputStream = null;
        BufferedReader in = null;
        InputStream is = null;
        long tam = 0;
        try {
            in = new BufferedReader(new InputStreamReader(clientes.get(id).getInputStream()));
            is = clientes.get(id).getInputStream();

            System.out.println("Recebido pedido para receber: " + nomeFicheiro);

            String nomeCaminhoCanonico = new File(localDir + File.separator + nomeFicheiro).getCanonicalPath();

            if (!nomeCaminhoCanonico.startsWith(localDir.getCanonicalPath() + File.separator)) {

                System.out.println("Nao e' permitido aceder ao ficheiro " + nomeCaminhoCanonico + "!");
                System.out.println("A directoria de base nao corresponde a " + localDir.getCanonicalPath() + "!");

            } else {
                requestedFileOutputStream = new FileOutputStream(nomeCaminhoCanonico);
                System.out.println("Ficheiro " + nomeFicheiro + " aberto para escrita.");

                while ((nbytes = is.read(fileChunck)) > 0) {
                    requestedFileOutputStream.write(fileChunck, 0, nbytes);
                    requestedFileOutputStream.flush();
                    tam += nbytes;
                }
                System.out.println("Ficheiro Recebido");
                requestedFileOutputStream.close();
            }
        } catch (IOException ex) {
            Logger.getLogger(Repositorio.class.getName()).log(Level.SEVERE, null, ex);
        } finally {
            try {
                requestedFileOutputStream.close();
                in.close();
                is.close();
            } catch (IOException ex) {
                Logger.getLogger(Repositorio.class.getName()).log(Level.SEVERE, null, ex);
            }

        }

        return tam;
    }

    public void enviaFicheiro(int id, String nomeFicheiro) {
        FileInputStream fileInput = null;
        File localDir = new File(localDirectory);
        OutputStream out = null;
        //BufferedReader in = null;
        try {
            out = clientes.get(id).getOutputStream();

            String nomeCaminhoCanonico = new File(localDirectory + File.separator + nomeFicheiro).getCanonicalPath();
            fileInput = new FileInputStream(nomeCaminhoCanonico);
            System.out.println("Ficheiro " + nomeCaminhoCanonico + " aberto para leitura.");

            while ((nbytes = fileInput.read(fileChunck)) > 0) {
                out.write(fileChunck, 0, nbytes);
                out.flush();
            }
            System.out.println("Ficheiro enviado");
        } catch (IOException ex) {
            Logger.getLogger(Repositorio.class.getName()).log(Level.SEVERE, null, ex);
        } finally {
            try {
                if (fileInput != null) {
                    fileInput.close();
                }
                out.close();
                //in.close();
            } catch (IOException ex) {
                Logger.getLogger(Repositorio.class.getName()).log(Level.SEVERE, null, ex);
            }

        }
    }

    public static int EncontraIndexFicheiro(String nome) {
        int i = 0;
        Boolean found = false;
        for (Ficheiro fich : file) {
            if (fich.nome.equals(nome)) {
                found = true;
                break;
            }
            i++;
        }
        if (found) {
            return i;
        } else {
            return -1;
        }
    }

    void setNewMissao() {
        t2 = new GereCliente();
    }

    static String toStringListaFicheiros() {
        String Mensagem = "";
        for (Ficheiro f : file) {
            if (f.file.isFile()) {
                Mensagem += "File " + f.file.getName() + "Date: " + f.getDataCriacao() + " Tamanho: " + f.getSize() + "\n";
            } else if (f.file.isDirectory()) {
                Mensagem += "Directory " + f.file.getName() + "Date: " + f.getDataCriacao() + " Tamanho: " + f.getSize() + "\n";
            }
        }
        return Mensagem;
    }

    public static void main(String[] args) {
        /*
         if (args.length != 3) {
            System.out.println("Erro de Sintaxe");
            System.err.println("Sintaxe: <Repositorio> <PortoEscuta> <IPserv> <DiretoriaRepositorio>");
            return;
         }

         try {
         UDPport = Integer.parseInt(args[0]);
         localDirectory = args[2].trim();
         IPserv = InetAddress.getByName(args[1].trim());
         } catch (UnknownHostException ex) {
         Logger.getLogger(Repositorio.class.getName()).log(Level.SEVERE, null, ex);
         }
         */
       
        int flag;
        int tcpPort = 9001; // Valor por omissão 9001
        ObjectInputStream in;
        Repositorio r = new Repositorio(localDirectory, 56000, "127.0.0.1");
        //Repositorio r = new Repositorio("C:\\Users\\Rui Margarido\\Dropbox\\PD\\TP_pdist\\repositorio1", 56000, "127.0.0.1");
        //Repositorio r = new Repositorio("C:\\Users\\NuNo\\Dropbox\\PD\\TP_pdist\\repositorio1", 56000, "127.0.0.1");
        r.t.start();
        try {
            dataSock2 = new DatagramSocket(41000); //41000
            dataPack2 = new DatagramPacket(new byte[256], 256, IPserv, 60000);
            dataSock2.send(dataPack2);
            dataPack2 = new DatagramPacket(new byte[12480], 12480);
            dataSock2.receive(dataPack2);
            in = new ObjectInputStream(new ByteArrayInputStream(dataPack2.getData(), 0, dataPack2.getLength()));
            MensagemRepositorio mensagem = (MensagemRepositorio) in.readObject();
            file = mensagem.file;
            sockR = new ServerSocket((tcpPort));
            idPrincipalRepositorio = mensagem.id;
            servSockRep = new ServerSocket(TCPportRep + idPrincipalRepositorio);
            System.out.println(toStringListaFicheiros());
            t3.start();

            if (!mensagem.portos.isEmpty()) {
                for (int i = 0; i < mensagem.ips.size(); i++) {
                    repositorios.add(new Socket(mensagem.ips.get(i), mensagem.portos.get(i)));
                }
            }

            while (true) {
                dataSock2.receive(dataPack2);
                String msg1 = new String(dataPack2.getData(), 0, dataPack2.getLength());
                System.out.println("msg1 ->  " + msg1);
                flag = Integer.parseInt(msg1);
                System.out.println(flag);
                //apagar
                dataSock2.receive(dataPack2);
                String msg2 = new String(dataPack2.getData(), 0, dataPack2.getLength());
                System.out.println("msg2 ->  " + msg2);
                if (flag == 1 || flag == 2) {
                    clientes.add(sockR.accept());
                    r.t2.setThisId(id);
                    id++;
                }
                r.setNewMissao();
                r.t2.setFlag(flag);
                r.t2.setNomeFicheiro(msg2);
                r.t2.start();

            }

        } catch (SocketException ex) {
            Logger.getLogger(Repositorio.class.getName()).log(Level.SEVERE, null, ex);
        } catch (UnknownHostException ex) {
            Logger.getLogger(Repositorio.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(Repositorio.class.getName()).log(Level.SEVERE, null, ex);
        } catch (ClassNotFoundException ex) {
            Logger.getLogger(Repositorio.class.getName()).log(Level.SEVERE, null, ex);
        }

    }
}
